#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""CTP API 代码生成准备脚本
用于创建软连接映射，以便SWIG命令可以引用到所需的文件

使用方法:
     python prepare_gen.py v6.7.11_20250617\\win64
     python prepare_gen.py v6.6.1_P1_20210406/linux
     python prepare_gen.py --clean                     # 清理软连接
     python prepare_gen.py --swig                      # 执行SWIG命令
"""

import os
import sys
import argparse
import platform
from pathlib import Path


def setup_console_encoding():
    """设置控制台字符编码为UTF-8"""
    if platform.system().lower() == 'windows':
        try:
            # 设置控制台输出编码为UTF-8
            os.system('chcp 65001 >nul 2>&1')
            # 设置Python的标准输出编码
            if hasattr(sys.stdout, 'reconfigure'):
                sys.stdout.reconfigure(encoding='utf-8')
            if hasattr(sys.stderr, 'reconfigure'):
                sys.stderr.reconfigure(encoding='utf-8')
        except Exception:
            # 如果设置失败，静默忽略
            pass


def is_windows():
    """检查是否为Windows系统"""
    return platform.system().lower() == 'windows'


def create_symlink(source, target):
    """创建软连接/符号链接或复制文件"""
    try:
        source_path = Path(source).resolve()
        target_path = Path(target)
        
        # 确保目标目录存在
        target_path.parent.mkdir(parents=True, exist_ok=True)
        
        # 如果目标已存在，先删除
        if target_path.exists() or target_path.is_symlink():
            if target_path.is_dir():
                target_path.rmdir()
            else:
                target_path.unlink()
        
        if is_windows():
            # Windows下直接复制文件，避免权限问题
            import shutil
            if source_path.is_dir():
                shutil.copytree(source_path, target_path)
                print(f"✅ 复制目录: {target_path.name} <- {source_path}")
            else:
                shutil.copy2(source_path, target_path)
                print(f"✅ 复制文件: {target_path.name} <- {source_path}")
        else:
            # Linux/Unix下创建软连接
            target_path.symlink_to(source_path)
            print(f"✅ 创建软连接: {target_path} -> {source_path}")
        
        return True
        
    except Exception as e:
        print(f"❌ 操作失败: {target_path.name} <- {source_path}")
        print(f"   错误: {e}")
        return False



def get_files_from_depend():
    """从.depend文件中获取需要映射的文件列表"""
    script_dir = Path(__file__).parent.resolve()
    depend_file = script_dir / '.depend'
    files_to_link = []
    
    if not depend_file.exists():
        print("⚠️  未找到.depend文件，使用默认文件列表")
        return [
            "ThostFtdcMdApi.h",
            "ThostFtdcTraderApi.h", 
            "ThostFtdcUserApiDataType.h",
            "ThostFtdcUserApiStruct.h",
            "thostmduserapi_se.dll",
            "thosttraderapi_se.dll",
            "thostmduserapi_se.lib", 
            "thosttraderapi_se.lib"
        ]
    
    try:
        with open(depend_file, 'r', encoding='utf-8') as f:
            content = f.read()
        
        # 只读取"实际存在的文件"部分
        in_actual_files = False
        for line in content.split('\n'):
            line = line.strip()
            if line.startswith('# 实际存在的文件'):
                in_actual_files = True
                continue
            elif line.startswith('#') and in_actual_files:
                break
            elif in_actual_files and line and not line.startswith('#'):
                # 只添加文件名，去掉路径前缀
                filename = Path(line).name
                files_to_link.append(filename)
        
        if not files_to_link:
            print("⚠️  .depend文件中没有找到实际存在的文件，使用默认列表")
            return [
                "ThostFtdcMdApi.h",
                "ThostFtdcTraderApi.h", 
                "ThostFtdcUserApiDataType.h",
                "ThostFtdcUserApiStruct.h",
                "thostmduserapi_se.dll",
                "thosttraderapi_se.dll",
                "thostmduserapi_se.lib", 
                "thosttraderapi_se.lib"
            ]
            
        return files_to_link
        
    except Exception as e:
        print(f"⚠️  读取.depend文件失败: {e}，使用默认文件列表")
        return [
            "ThostFtdcMdApi.h",
            "ThostFtdcTraderApi.h", 
            "ThostFtdcUserApiDataType.h",
            "ThostFtdcUserApiStruct.h",
            "thostmduserapi_se.dll",
            "thosttraderapi_se.dll",
            "thostmduserapi_se.lib", 
            "thosttraderapi_se.lib"
        ]


def prepare_generation_environment(source_dir):
    """准备代码生成环境"""
    print(f"🚀 准备CTP API代码生成环境")
    print(f"📂 源目录: {source_dir}")
    print("=" * 60)
    
    # 获取当前脚本所在目录（gen_tools目录）
    script_dir = Path(__file__).parent.resolve()
    project_root = script_dir.parent
    
    # 源目录路径（支持 linux 与 linux64 互相回退）
    source_path = project_root / source_dir
    if not source_path.exists():
        # 当用户传入 linux64 但标准版目录为 linux 时，尝试回退
        alt = None
        src_str = str(source_path)
        if 'linux64' in src_str:
            alt = Path(str(source_path).replace('linux64', 'linux'))
        elif os.sep + 'linux' + os.sep in src_str:
            alt = Path(str(source_path).replace(os.sep + 'linux' + os.sep, os.sep + 'linux64' + os.sep))
        if alt and alt.exists():
            print(f"ℹ️  源目录不存在，回退使用: {alt}")
            source_path = alt
        else:
            print(f"❌ 源目录不存在: {source_path}")
            return False
    
    print(f"📍 项目根目录: {project_root}")
    print(f"📍 工具目录: {script_dir}")
    print(f"📍 源目录: {source_path}")
    print()
    
    # 生成依赖文件
    generate_depend_file(source_path)


    # 获取需要映射的文件列表
    files_to_link = get_files_from_depend()
    
    # 添加so文件到映射列表（如果存在）
    additional_files = []
    print(f"📁 扫描源目录中的so文件: {source_path}")
    for file_path in source_path.iterdir():
        if file_path.is_file() and file_path.name.endswith('.so'):
            print(f"   发现so文件: {file_path.name}")
            if file_path.name not in files_to_link:
                additional_files.append(file_path.name)
    
    if additional_files:
        files_to_link.extend(additional_files)
        print(f"ℹ️  添加额外的so文件到映射列表: {additional_files}")
    else:
        print("ℹ️  没有发现额外的so文件")
    
    # 可选的工具文件
    optional_files = [
        "CMakeLists.txt",
    ]
    
    success_count = 0
    total_count = 0
    
    print("📋 创建必需文件的软连接:")
    for filename in files_to_link:
        source_file = source_path / filename
        target_file = script_dir / filename
        
        total_count += 1
        if source_file.exists():
            if create_symlink(source_file, target_file):
                success_count += 1
        else:
            print(f"⚠️  文件不存在，跳过: {filename}")
    
    print(f"\n📋 创建可选文件的软连接:")
    for filename in optional_files:
        source_file = source_path / filename
        target_file = script_dir / filename
        
        if source_file.exists():
            create_symlink(source_file, target_file)
            print(f"✅ 可选文件已链接: {filename}")
        else:
            print(f"ℹ️  可选文件不存在: {filename}")
    
    # 创建tools目录的软连接（如果存在）
    tools_source = project_root / "tools"
    tools_target = script_dir / "tools"
    if tools_source.exists():
        create_symlink(tools_source, tools_target)
        print(f"✅ 工具目录已链接: tools/")
    
    # 在 Linux/Unix 下：为非 lib 前缀的 .so 创建 lib 前缀软链接，便于链接器查找
    # 同时处理反向情况：为lib前缀的so文件创建无lib前缀的链接
    if not is_windows():
        try:
            for so_file in script_dir.glob("*.so"):
                if not so_file.name.startswith("lib"):
                    # 为非lib前缀的文件创建lib前缀链接
                    lib_name = f"lib{so_file.name}"
                    lib_path = script_dir / lib_name
                    if not lib_path.exists():
                        lib_path.symlink_to(so_file)
                        print(f"✅ 创建lib前缀软链接: {lib_name} -> {so_file.name}")
                else:
                    # 为lib前缀的文件创建无lib前缀链接（便于某些编译器查找）
                    non_lib_name = so_file.name[3:]  # 移除'lib'前缀
                    if non_lib_name and not (script_dir / non_lib_name).exists():
                        non_lib_path = script_dir / non_lib_name
                        non_lib_path.symlink_to(so_file)
                        print(f"✅ 创建无lib前缀软链接: {non_lib_name} -> {so_file.name}")
        except Exception as e:
            print(f"⚠️  创建 lib*.so 软链接失败: {e}")
    

    
    print("\n" + "=" * 60)
    print(f"📊 统计信息:")
    print(f"   必需文件: {success_count}/{total_count} 成功")
    
    if success_count == total_count:
        print("🎉 所有必需文件都已成功创建软连接！")
        print("\n💡 现在可以在gen_tools目录下执行SWIG命令:")
        print("   swig -c++ -python thostmduserapi.i")
        print("   swig -c++ -python thosttraderapi.i")
        print("\n💡 或者直接运行编译脚本:")
        print("   python compile_simple.py")
        print("   python compile_pyd.py")
        return True
    else:
        print(f"⚠️  有 {total_count - success_count} 个必需文件未能创建软连接")
        return False


def clean_symlinks():
    """清理所有软连接和复制的文件"""
    script_dir = Path(__file__).parent.resolve()
    
    print("清理gen_tools目录中的软连接和复制文件...")
    
    # 要清理的文件模式
    patterns_to_clean = [
        "*.h", "*.dll", "*.lib", "*.so", "*.cxx", "*.py", "ThostFtdc*"
    ]
    
    cleaned_count = 0
    
    for pattern in patterns_to_clean:
        for file_path in script_dir.glob(pattern):
            # 跳过脚本自身和.i文件以及编译脚本
            skip_files = ['prepare_gen.py', 'compile_simple.py', 'compile_pyd.py', 'install_complete.py', 'one_click_build_deploy.py']
            if file_path.name in skip_files or file_path.suffix == '.i':
                continue
                
            if file_path.exists():
                file_path.unlink()
                print(f"🗑️  删除文件: {file_path.name}")
                cleaned_count += 1
    
    # 清理tools目录
    tools_dir = script_dir / "tools"
    if tools_dir.exists():
        import shutil
        shutil.rmtree(tools_dir)
        print(f"🗑️  删除目录: tools/")
        cleaned_count += 1
    
    # 清理其他可能的文件
    other_files = []
    for filename in other_files:
        file_path = script_dir / filename
        if file_path.exists() and file_path.name != 'build_pyd.bat':  # 保留原始的build_pyd.bat
            file_path.unlink()
            print(f"🗑️  删除文件: {filename}")
            cleaned_count += 1
    
    print(f"✅ 清理完成，共删除 {cleaned_count} 个文件/目录")


def clean_generated_files():
    """清理SWIG生成的文件"""
    script_dir = Path(__file__).parent.resolve()
    
    print("清理SWIG生成的文件...")
    
    # SWIG生成的文件模式
    generated_patterns = [
        "*_wrap.cxx",
        "*_wrap.h", 
        "thostmduserapi.py",
        "thosttraderapi.py"
    ]
    
    cleaned_count = 0
    
    for pattern in generated_patterns:
        for file_path in script_dir.glob(pattern):
            if file_path.exists():
                file_path.unlink()
                print(f"🗑️  删除生成文件: {file_path.name}")
                cleaned_count += 1
    
    # 清理编译生成的.pyd文件
    pyd_patterns = ["*.pyd", "*_d.pyd"]
    for pattern in pyd_patterns:
        for file_path in script_dir.glob(pattern):
            if file_path.exists():
                file_path.unlink()
                print(f"🗑️  删除编译文件: {file_path.name}")
                cleaned_count += 1
    
    # 清理编译临时目录
    temp_dirs = ["build", "__pycache__"]
    for dir_name in temp_dirs:
        temp_dir = script_dir / dir_name
        if temp_dir.exists():
            import shutil
            shutil.rmtree(temp_dir)
            print(f"🗑️  删除临时目录: {dir_name}/")
            cleaned_count += 1
    
    # 清理编译临时文件
    temp_files = ["*.obj", "*.exp", "*.lib", "*.manifest"]
    for pattern in temp_files:
        for file_path in script_dir.glob(pattern):
            # 跳过原始的.lib文件（以_se.lib结尾的）
            if file_path.name.endswith('_se.lib'):
                continue
            if file_path.exists():
                file_path.unlink()
                print(f"🗑️  删除临时文件: {file_path.name}")
                cleaned_count += 1
    
    if cleaned_count == 0:
        print("ℹ️  没有找到需要清理的生成文件")
    else:
        print(f"✅ 清理完成，共删除 {cleaned_count} 个生成文件/目录")


def clean_all():
    """清理所有文件（复制的文件 + 生成的文件）"""
    print("🧹 执行完整清理...")
    clean_generated_files()
    print()
    clean_symlinks()
    print("\n🎉 完整清理完成！")


def convert_cxx_to_gbk(file_path):
    """将 .cxx 文件从 UTF-8 转换为 GBK 编码（仅在 Windows 下）"""
    if not is_windows():
        return True
        
    try:
        # 读取文件内容（假设原文件是 UTF-8 编码）
        with open(file_path, 'r', encoding='utf-8', errors='ignore') as f:
            content = f.read()
        
        # 以 GBK 编码重新保存
        with open(file_path, 'w', encoding='gbk', errors='ignore') as f:
            f.write(content)
        
        print(f"   🔄 已转换为 GBK 编码: {file_path.name}")
        return True
        
    except Exception as e:
        print(f"   ⚠️  编码转换失败 {file_path.name}: {e}")
        return False


def generate_depend_file(source_path):
    """生成依赖文件.depend，记录所有依赖文件和项目类型"""
    try:
        script_dir = Path(__file__).parent.resolve()
        depend_file = script_dir / ".depend"
        
        print("\n📋 生成依赖文件 .depend...")
        
        # 根据路径确定项目类型
        project_type = "ctp"  # 默认
        source_str = str(source_path)
        if "ctpmini" in source_str.lower():
            project_type = "ctpmini"
        elif "ctp" in source_str.lower():
            project_type = "ctp"
        
        # 定义可能的依赖文件
        possible_deps = [
            # 头文件
            "ThostFtdcMdApi.h",
            "ThostFtdcTraderApi.h", 
            "ThostFtdcUserApiDataType.h",
            "ThostFtdcUserApiStruct.h",
            
            # Windows库文件
            "thostmduserapi_se.dll",
            "thosttraderapi_se.dll",
            "thostmduserapi_se.lib", 
            "thosttraderapi_se.lib",
            
            # Linux库文件
            "libthostmduserapi_se.so",
            "libthosttraderapi_se.so",
            
            # macOS库文件
            "libthostmduserapi_se.dylib",
            "libthosttraderapi_se.dylib"
        ]
        
        # 收集实际存在的文件
        actual_files = []
        
        # 扫描目录中实际存在的所有相关文件
        print(f"扫描源目录: {source_path}")
        for file_path in source_path.iterdir():
            if file_path.is_file():
                filename = file_path.name
                print(f"发现文件: {filename}")
                # 包含所有CTP API相关文件：头文件、DLL、LIB、SO（包括lib前缀的）
                if any(filename.startswith(prefix) for prefix in ['ThostFtdc', 'thostmduserapi', 'thosttraderapi', 'libthostmduserapi', 'libthosttraderapi']):
                    if filename.endswith(('.h', '.dll', '.lib', '.so', '.dylib')):
                        actual_files.append(filename)
                        print(f"添加文件到依赖列表: {filename}")
        
        # 确保包含基本依赖文件（如果存在）
        for dep in possible_deps:
            full_path = source_path / dep
            if full_path.exists() and dep not in actual_files:
                actual_files.append(dep)
        
        # 确保包含当前目录中的so文件
        for file_path in source_path.iterdir():
            if file_path.is_file() and file_path.name.endswith('.so'):
                if file_path.name not in actual_files:
                    actual_files.append(file_path.name)
        
        # # 同时扫描gen_tools目录中已有的so文件（软链接）
        # script_dir = Path(__file__).parent.resolve()
        # for file_path in script_dir.iterdir():
        #     if file_path.is_file() and file_path.name.endswith('.so'):
        #         if file_path.name not in actual_files:
        #             actual_files.append(file_path.name)
        
        # 拷贝上级目录的.i文件
        parent_dir = source_path.parent
        i_files = list(parent_dir.glob("*.i"))
        if i_files:
            print(f"📁 拷贝.i文件从 {parent_dir} 到 gen_tools...")
            for i_file in i_files:
                target_file = script_dir / i_file.name
                if i_file.exists():
                    import shutil
                    shutil.copy2(i_file, target_file)
                    print(f"✅ 拷贝: {i_file.name}")
                    # 将.i文件也添加到依赖列表
                    if i_file.name not in actual_files:
                        actual_files.append(i_file.name)
        
        with open(depend_file, 'w', encoding='utf-8') as f:
            f.write("# CTP API 依赖文件列表\n")
            f.write(f"# 由 prepare_gen.py 分析 {source_path.name} 目录生成\n")
            f.write(f"# 项目类型: {project_type}\n")
            f.write("# 供 compile_simple.py 使用\n\n")
            
            # 写入项目类型
            f.write("# 项目类型\n")
            f.write(f"PROJECT_TYPE={project_type}\n\n")
            
            # 写入实际存在的文件
            f.write("# 实际存在的文件\n")
            for filename in sorted(actual_files):
                f.write(f"{filename}\n")
                
            # 写入所有可能的依赖文件（作为参考）
            f.write("\n# 所有可能的依赖文件\n")
            for dep in possible_deps:
                full_path = source_path / dep
                status = "存在" if full_path.exists() else "不存在"
                f.write(f"# {dep} [{status}]\n")
        
        print(f"✅ 依赖文件已生成: {depend_file} (项目类型: {project_type})")
        return True
    except Exception as e:
        print(f"❌ 生成依赖文件失败: {e}")
        return False


def run_swig_commands():
    """执行SWIG命令生成包装代码"""
    script_dir = Path(__file__).parent.resolve()
    
    print("\n🔧 执行SWIG命令生成包装代码...")
    
    # 选择 SWIG 可执行文件：
    # - Windows 优先使用本地 swig.exe；若不存在则尝试系统 PATH 中的 swig
    # - Linux/macOS 使用系统 PATH 中的 swig
    swig_cmd = None
    if is_windows():
        local_exe = script_dir / "swig.exe"
        if local_exe.exists():
            swig_cmd = str(local_exe)
        else:
            from shutil import which
            swig_cmd = which("swig")
    else:
        from shutil import which
        swig_cmd = which("swig")
    
    if not swig_cmd:
        print("❌ 错误: 未找到 SWIG 可执行程序。请安装 swig 并确保其在 PATH 中。")
        return False
    
    # 要处理的.i文件
    i_files = ['thostmduserapi.i', 'thosttraderapi.i']
    
    success_count = 0
    
    for i_file in i_files:
        i_path = script_dir / i_file
        if not i_path.exists():
            print(f"❌ 错误: 找不到 {i_file}")
            continue
            
        print(f"🔄 处理 {i_file}...")
        
        # 构建SWIG命令
        cmd = [swig_cmd, "-threads", "-c++", "-python", i_file]
        
        try:
            import subprocess
            result = subprocess.run(
                cmd, 
                cwd=script_dir,
                stdout=subprocess.PIPE,
                stderr=subprocess.PIPE,
                text=True,
                encoding='utf-8',
                errors='replace'
            )
            
            if result.returncode == 0:
                print(f"✅ {i_file} 处理成功")
                success_count += 1
                
                # 显示生成的文件
                base_name = i_file.replace('.i', '')
                wrap_cxx = f"{base_name}_wrap.cxx"
                wrap_py = f"{base_name}.py"
                
                if (script_dir / wrap_cxx).exists():
                    print(f"   📄 生成: {wrap_cxx}")
                    # 在 Windows 环境下将 .cxx 文件转换为 GBK 编码
                    convert_cxx_to_gbk(script_dir / wrap_cxx)
                    
                if (script_dir / wrap_py).exists():
                    print(f"   📄 生成: {wrap_py}")
                    
            else:
                print(f"❌ {i_file} 处理失败")
                if result.stderr:
                    print(f"   错误信息: {result.stderr.strip()}")
                if result.stdout:
                    print(f"   输出信息: {result.stdout.strip()}")
                    
        except Exception as e:
            print(f"❌ 执行SWIG命令时出错: {e}")
    
    if success_count == len(i_files):
        print(f"\n🎉 所有SWIG命令执行成功! ({success_count}/{len(i_files)})")
        print("\n💡 现在可以运行编译脚本:")
        print("   python compile_simple.py")
        print("   python compile_pyd.py")
        return True
    else:
        print(f"\n⚠️  部分SWIG命令执行失败 ({success_count}/{len(i_files)})")
        return False


def main():
    """主函数"""
    # 设置控制台字符编码
    setup_console_encoding()
    
    parser = argparse.ArgumentParser(
        description='CTP API 代码生成准备脚本',
        formatter_class=argparse.RawDescriptionHelpFormatter,
        epilog="""
使用示例:
  python prepare_gen.py v6.7.11_20250617\\win64    # 准备生成环境
  python prepare_gen.py --clean                     # 清理复制的文件
  python prepare_gen.py --clean-gen                 # 清理生成的文件
  python prepare_gen.py --clean-all                 # 完整清理
  python prepare_gen.py --swig                      # 执行SWIG生成
        """
    )
    
    parser.add_argument(
        'source_dir', 
        nargs='?',
        help='源目录路径，相对于项目根目录 (例如: v6.7.11_20250617\\win64)'
    )
    
    parser.add_argument(
        '--clean', 
        action='store_true',
        help='清理复制的文件和目录'
    )
    
    parser.add_argument(
        '--clean-gen', 
        action='store_true',
        help='清理SWIG和编译生成的文件'
    )
    
    parser.add_argument(
        '--clean-all', 
        action='store_true',
        help='清理所有文件（复制的 + 生成的）'
    )
    
    parser.add_argument(
        '--swig', 
        action='store_true',
        help='执行SWIG命令生成包装代码'
    )
    
    args = parser.parse_args()
    
    if args.clean:
        clean_symlinks()
        return
    
    if args.clean_gen:
        clean_generated_files()
        return
    
    if args.clean_all:
        clean_all()
        return
    
    if args.swig:
        success = run_swig_commands()
        if not success:
            sys.exit(1)
        return
    
    if not args.source_dir:
        parser.print_help()
        print("\n❌ 错误: 请指定源目录路径")
        sys.exit(1)
    
    # 标准化路径分隔符
    source_dir = args.source_dir.replace('/', os.sep).replace('\\', os.sep)
    
    success = prepare_generation_environment(source_dir)
    
    if not success:
        sys.exit(1)


if __name__ == '__main__':
    main()